import { CloneAble } from "./data_ext";
import Utils from "./utils";

export enum CSMemberType {
    Single,
    Array,
    Collection,
};

export class ClassSerializeMemberInfo {
    key = ""; //成员名
    ctorName = ""; //构造名，为空时数据为基础类型。
    type: CSMemberType = CSMemberType.Single; //Single单个，Array是数组，Collection 是 {}
};

// 记录类的序列化结构
export class ClassSerializeInfo {
    ctor: Function = null; //类构造
    memberList: ClassSerializeMemberInfo[] = [];
    parent: ClassSerializeInfo = null; //父类CSI
    children: ClassSerializeInfo[] = []; //子类CSI

    initExtends = false; //是否已经包含了继承节点的CSI数据。

    constructor(ctor) {
        this.ctor = ctor;
    }
    preAddSerializeList: [string, any, CSMemberType][] = [];
    preAddSerialize(key: string, ctor: any = null, type = CSMemberType.Single) {
        this.preAddSerializeList.push([key, ctor, type]);
    }
    addSerialize(key: string, ctorName: string = "", type: CSMemberType) {
        let mInfo = new ClassSerializeMemberInfo();
        mInfo.ctorName = ctorName;
        mInfo.key = key;
        mInfo.type = type;
        this.memberList.push(mInfo);
    }
};
export const ClassSerializeInfoMap = new Map<any, ClassSerializeInfo>(); //类的序列化结构 MAP
export const NameClassMap = new Map<string, new () => any>(); //名 -> 类构造
export const ClassNameMap: Map<new () => any, string> = new Map(); // 类构造 -> 名
export function Class2Name(ctor: new () => any) {// 类构造 -> 名
    return ClassNameMap.get(ctor);
}
export function Name2Class(cn: string) {//名 -> 类构造
    return NameClassMap.get(cn);
}
export function SerializeAble2Name<T extends SerializeAble>(sa: T) {
    return <string>sa.constructor["$cn"];
}
export function SerializeAble2Class<T extends SerializeAble>(sa: T) {
    return n2c(sa.constructor["$cn"]);
}
export function GetCSIByClass(ctor: any) { //类 -> 序列化结构
    let parent = Object.getPrototypeOf(ctor);
    let csi = ClassSerializeInfoMap.get(ctor);
    if (!csi.initExtends) {
        while (parent && parent.name !== "SerializeAble") {
            let pcsi = ClassSerializeInfoMap.get(parent);
            parent = Object.getPrototypeOf(parent);
            if (pcsi) {
                if (!csi.parent) {
                    console.log(csi.ctor["$cn"], "extends", pcsi.ctor["$cn"]);
                    csi.parent = pcsi;
                    pcsi.children.push(csi);
                }
                csi.memberList = csi.memberList.concat(pcsi.memberList);
            }
        }
        csi.initExtends = true;
    }
    // console.log(`CSI<${ctor.name}> `, csi);
    return csi;
};
export function GetCSI(cn: string) { //类名 -> 序列化结构
    return GetCSIByClass(n2c(cn));
}
export const n2c = Name2Class;
export const c2n = Class2Name;
export const obj2c = SerializeAble2Class;
export const obj2n = SerializeAble2Name;
window["cn"] = <any>{
    "csis": ClassSerializeInfoMap,
    "n2c": n2c,
    "c2n": c2n,
    "obj2c": obj2c,
    "obj2n": obj2n,
    "GetCSIByClass": GetCSIByClass,
    "GetCSI": GetCSI,
};
//成员序列化，CLS为成员类型
export function Serialize<T>(cls?: new () => T, type = CSMemberType.Single) {
    return (target: Object, property: string): void => {
        if (!ClassSerializeInfoMap.get(target.constructor)) {
            ClassSerializeInfoMap.set(target.constructor, new ClassSerializeInfo(target.constructor));
        }
        let csi = ClassSerializeInfoMap.get(target.constructor);
        csi.preAddSerialize(property, cls, type);
    };
}
//类修饰器
export function SerializeClass(regClassName: string) {
    return function (ctor: any): void {
        if (!ClassSerializeInfoMap.get(ctor)) {
            ClassSerializeInfoMap.set(ctor, new ClassSerializeInfo(ctor));
        }
        ctor.$cn = regClassName;
        NameClassMap.set(ctor.$cn, ctor);
        ClassNameMap.set(ctor, ctor.$cn);

        let csi = ClassSerializeInfoMap.get(ctor);
        csi.preAddSerializeList.forEach(kv => {
            let [property, cls, type] = kv;
            csi.addSerialize(property, cls ? cls["$cn"] : "", type);
            // console.log(`${ctor.name} addSerialize: ${property}(${cls ? cls["$cn"] : "base value"})`);
        });

    }
}

export default abstract class SerializeAble implements CloneAble {
    //序列化
    toJSON(): any {
        let json: any = {
            $cn: this.constructor["$cn"],
        };
        let _assign = (dat: any, property: string | number, obj: any) => {
            if (obj[property] !== undefined && obj[property] !== null) {
                if (obj[property].toJSON) {
                    dat[property] = obj[property].toJSON();
                }
                else if (typeof obj[property] === "object" && obj[property] instanceof Array) {
                    let list = [];
                    obj[property].forEach((ele, ind) => {
                        _assign(list, ind, obj[property]);
                    });
                    dat[property] = list;
                }
                else {
                    dat[property] = obj[property];
                }
            }
        };
        let csi = GetCSIByClass(this.constructor);
        csi.memberList.forEach(mInfo => {
            // let cls = null;
            // if (mInfo.ctorName) {
            //     cls = n2c(mInfo.ctorName);
            // }
            switch (mInfo.type) {
                case CSMemberType.Single:
                    _assign(json, mInfo.key, this);
                    break;
                case CSMemberType.Array:
                    json[mInfo.key] = [];
                    this[mInfo.key].forEach((item, ind) => {
                        _assign(json[mInfo.key], ind, this[mInfo.key]);
                    });
                    break;
                case CSMemberType.Collection:
                    json[mInfo.key] = {};
                    for (let key in this[mInfo.key]) {
                        _assign(json[mInfo.key], key, this[mInfo.key]);
                    }
                    break;
            }
        })
        // csi.memberList.forEach(sKey => {
        //     _assign(json, sKey, this);
        // });
        return json;
    }
    assignFromJSON(json: any) {
        if (typeof json === "string") {
            json = JSON.parse(json);
        }
        let _assign = (obj: Object, property: string | number, cls: new () => SerializeAble, dat: any) => {
            if (dat === undefined || dat === null) {
                return;
            }
            if (typeof dat === "number" || typeof dat === "string" || typeof dat === "boolean") {
                obj[property] = dat;
                return;
            }
            if (typeof dat === "object") {
                let cn = dat.$cn || dat.__cn;
                let $cls = cn ? n2c(cn) : null;

                if (dat instanceof Array) {
                    let list = [];
                    dat.forEach((dc, ind) => {
                        _assign(list, ind, cls, dc);
                    });
                    obj[property] = list;
                }

                else if (cls || $cls) {
                    if (obj[property] && obj[property].assignFromJSON) {
                        obj[property].assignFromJSON(dat);
                    }
                    else {
                        if ($cls) {
                            cls = $cls;
                        }
                        let prop = new cls();
                        prop.assignFromJSON(dat);
                        obj[property] = prop;
                    }
                }
                else if (obj[property] && obj[property].assignFromJSON) {
                    obj[property].assignFromJSON(dat);
                }
                else {
                    obj[property] = {};
                    for (let key in dat) {
                        _assign(obj[property], key, undefined, dat[key]);
                    }
                }
            }
        };
        if (json) {
            let csi = GetCSIByClass(this.constructor);
            csi.memberList.forEach(mInfo => {
                let dat = json[mInfo.key];
                let cls: new () => SerializeAble;
                if (mInfo.ctorName) {
                    cls = n2c(mInfo.ctorName);
                }
                switch (mInfo.type) {
                    case CSMemberType.Single:
                        _assign(this, mInfo.key, cls, dat);
                        break;
                    case CSMemberType.Array:
                        if (dat === undefined) {
                            dat = [];
                        }
                        this[mInfo.key] = [];
                        dat.forEach((datItem, ind) => {
                            _assign(this[mInfo.key], ind, cls, datItem);
                        });
                        break;
                    case CSMemberType.Collection:
                        if (dat === undefined) {
                            dat = {};
                        }
                        this[mInfo.key] = {};
                        for (let key in dat) {
                            _assign(this[mInfo.key], key, cls, dat[key]);
                        }
                        break;
                }
            });
        }
    }
    //反序列化
    static CreateFromJSON<T extends SerializeAble>(json: any, ctor: new () => T): T {
        let out: T;
        if (!ctor) {
            if (json && (json.$cn || json.__cn)) {
                ctor = n2c(json.$cn || json.__cn);
            }
        }

        if (ctor) {
            out = new ctor();
            out.assignFromJSON(json);
        }
        else {
            out = json;
        }
        return out as T;
    }
    clone(): CloneAble {
        return SerializeAble.Clone(this);
    }

    static Clone<T extends SerializeAble>(src: T): T {
        return SerializeAble.CreateFromJSON(JSON.parse(JSON.stringify(src)), <new () => T>src.constructor);
    }
};

export class StrDataReader {
    str = "";
    seek = 0;
    constructor(str: string) {
        this.str = str;
        this.seek = 0;
    }
    showSeek(range = 5) {
        let out = [];
        for (let i = 1; i < range && this.seek - i >= 0; i++) {
            out.push(this.str[this.seek - i]);
        }
        out = out.reverse();
        out.push("[");
        out.push(" ");
        out.push(this.str[this.seek]);
        out.push(" ");
        out.push("]");
        for (let i = 1; i < range && this.seek + i < this.str.length; i++) {
            out.push(this.str[this.seek + i]);
        }
        console.log(out.join(""));
    }
    isEnd() {
        return this.seek >= this.str.length;
    }
    read(len: number) {
        let str = this.str.substring(this.seek, this.seek + len);
        this.seek += len;
        return str;
    }
    readDynamicString(lenDigit = 2) {
        return this.read(this.readInt(lenDigit));
    }
    readUntil(char: string = "", includeChar = false) {
        let str = "";
        if (!char) {
            str = this.str.substring(this.seek)
            this.seek = this.str.length;
        }
        else {
            while (this.str[this.seek] !== char && this.seek < this.str.length) {
                str += this.str[this.seek++];
            }

            //"]"
            if (!this.isEnd()) {
                if (includeChar) {
                    str += this.str[this.seek++];
                }
                else {
                    this.seek++;
                }
            }
        }
        return str;
    }

    readCondition(cb: (tag: string, res: string) => void, ...condCharList: string[]) {
        let keepSeeking = this.seek < this.str.length;
        let str = "";

        while (keepSeeking) {
            for (let i = 0; i < condCharList.length; i++) {
                if (this.str[this.seek] === condCharList[i]) {
                    keepSeeking = false;
                    str += this.str[this.seek]; //加上最后一个字符
                    this.seek++; //步进
                    cb(condCharList[i], str);
                    break;
                }
            }
            if (keepSeeking) {
                str += this.str[this.seek++];
                keepSeeking = this.seek < this.str.length;
            }
        }
    }
    readInt(len: number) {
        return Number(this.read(len));
    }
    readSignInt(len: number) {
        if (len <= 1) {
            console.error("StrDataReader::readSignInt, len <= 1");
            return 0;
        }
        let signStr = this.read(1);
        let num = this.readInt(len - 1);
        return signStr === "0" ? num : -num;
    }
    readDynamicInt(lenDigit = 2) {
        let digit = this.readInt(lenDigit);
        return this.readInt(digit);
    }
    readDynamicSignInt(lenDigit = 2) {
        let digit = this.readInt(lenDigit);
        return this.readSignInt(digit);
    }
    readBool() {
        return this.read(1) === "1";
    }
};

export class StrDataWriter {
    str = "";

    write(val: string) {
        this.str += val;
        return this;
    }
    writeDynamicString(val: string, lenDigit = 2) {
        this.writeInt(val.length, lenDigit);
        this.write(val);
    }
    writeInt(int: number, digit: number) {
        this.str += Utils.NumberToDigitString(int, digit);
        return this;
    }

    writeDynamicInt(int: number, lenDigit = 2) {
        let digit = int.toFixed(0).length;
        this.writeInt(digit, lenDigit);
        this.writeInt(int, digit);
    }

    writeSignInt(int: number, digit: number) {
        if (digit <= 1) {
            console.error("StrDataWriter::writeSignInt, digit <= 1, 位数不够");
            return this;
        }
        let numStr = Utils.NumberToDigitString(int, digit - 1);
        numStr = numStr.replace("-", "");
        let signStr = int < 0 ? "1" : "0";
        this.str += signStr + numStr;
        return this;
    }
    writeDynamicSignInt(int: number, lenDigit = 2) {
        let digit = Math.abs(int).toFixed(0).length + 1;
        this.writeInt(digit, lenDigit);
        this.writeSignInt(int, digit);
    }

    writeBool(bool: boolean) {
        this.str += bool ? "1" : "0";
        return this;
    }
};

window["StrDataReader"] = StrDataReader;
window["StrDataWriter"] = StrDataWriter;